package utils

import (
	"fmt"
	"sync"
	"time"

	"github.com/sirupsen/logrus"
)

type parallelTask struct {
	tasks []ITask
	syncTask
}
type sequentTask struct {
	parallelTask
}

func NewParallelTask(name string) *parallelTask {
	p := &parallelTask{
		tasks: make([]ITask, 0),
		syncTask: syncTask{
			Name:    name,
			errChan: make(chan error),
		},
	}
	return p
}
func NewSequentTask(name string) *sequentTask {
	p := &sequentTask{
		parallelTask: *NewParallelTask(name),
	}
	return p
}
func (t *parallelTask) AddTask(task ITask) error {
	if task == nil {
		return ErrTaskNil
	}
	if t.status != TS_READY {
		return ErrTaskRunning
	}
	t.tasks = append(t.tasks, task)
	if task.GetTimeout() > t.Timeout {
		t.Timeout = task.GetTimeout()
	}
	return nil
}

func (t *parallelTask) Run() error {
	err := t.begin()
	if err != nil {
		return err
	}
	wg := sync.WaitGroup{}

	for _, task := range t.tasks {
		wg.Add(1)
		go func() {
			e := task.Run()
			wg.Done()
			if e != nil {
				t.errChan <- e
			}
		}()
	}
	chTimeout := time.AfterFunc(t.Timeout, func() {
		t.errChan <- ErrTaskTimeout
	})
	defer chTimeout.Stop()
	go func() {
		wg.Wait()
		t.errChan <- nil
	}()

	e := <-t.errChan
	t.end(e)
	return e
}

func (t *parallelTask) Succeed() bool {
	for _, task := range t.tasks {
		if !task.Succeed() {
			return false
		}
	}
	return true
}
func (t *parallelTask) Report(print bool) string {
	size := len(t.tasks)
	str := fmt.Sprintf("\nParallel task with %d children: name: %s, timeout: %v, succeed: %v, error: %v\n", size, t.Name, t.Timeout, t.Succeed(), t.err)
	for index, task := range t.tasks {
		str += fmt.Sprintf("\t[%d/%d]:\t%s", index+1, size, task.Report(false))
	}
	if print {
		logrus.Info(str)
	}
	return str
}
func (t *sequentTask) AddTask(task ITask) error {
	if task == nil {
		return ErrTaskNil
	}
	if t.IsRunning() {
		return ErrTaskRunning
	}
	t.tasks = append(t.tasks, task)
	t.Timeout += task.GetTimeout()
	return nil
}

func (t *sequentTask) Run() error {
	err := t.begin()
	if err != nil {
		return err
	}

	chTimeout := time.AfterFunc(t.Timeout, func() {
		t.errChan <- ErrTaskTimeout
	})
	defer chTimeout.Stop()
	go func() {
		for _, task := range t.tasks {
			e := task.Run()
			if e != nil {
				t.errChan <- e
				return
			}
		}
		t.errChan <- nil
	}()
	e := <-t.errChan
	t.end(e)
	return e
}
func (t *sequentTask) Report(print bool) string {
	size := len(t.tasks)
	str := fmt.Sprintf("\nSequent task with %d children: name: %s, timeout: %v, succeed: %v, error: %v\n", size, t.Name, t.Timeout, t.Succeed(), t.err)
	for index, task := range t.tasks {
		str += fmt.Sprintf("\t[%d/%d]: %s", index+1, size, task.Report(false))
	}
	if print {
		logrus.Info(str)
	}
	return str
}
